import { _decorator, Component, Label, Node, Prefab, view, instantiate, tween, Vec3, color, game } from 'cc';
import { Block } from './Block';
import { ScoreManager } from './ScoreManager';
const { ccclass, property } = _decorator;


// 游戏大小 4x4格子
const size = 4;
// 每次生成的数字 2或4
const numbers = [2, 4]

// 进行移动的方块
interface MoveCell {
    oldLocation: number[]; // [i,j]
    newLocation?: number[];
    merged?: boolean; // 标记为合并 本轮已合并过的位置 不能连续合并
    mergeDeleted?: boolean; // 标记为被合并 将进行删除
}

@ccclass('Game')
export class Game extends Component {

    @property(Label)
    public scoreLabel: Label = null;
    @property(Label)
    public bestScoreLabel: Label = null;
    @property(Prefab)
    public blockPrefab: Prefab = null;

    @property(Node)
    public bg: Node = null;
    @property(Node)
    public restartPanel: Node = null;
    @property(Label)
    public gameOverLabel: Label = null;
    @property(Label)
    public gameTimeLabel: Label = null;
    @property(Node)
    public fireworks: Node = null;

    // 所有格子的位置 即背景地图
    private _positionsNodes: Node[][] = [];
    // 游戏数据
    private _data: number[][] = [];
    // 游戏数据对应的方块
    private _blocks: Node[][] = [];
    private blockSize = 200;
    private gap = 10;

    // 记录要移动的方块
    private toMoves: MoveCell[] = []; 

    // 本轮有移动过 则生成随机方块
    private hasMoved = false;
    // 游戏是否胜利 达成2048即胜利
    private isGameWon = false;
    // 游戏是否开始 进行计时
    private isGameStart = false;
    // 是否可以进入玩家控制
    private canControl = false;
    private startTime = 0;


    protected onLoad(): void {
        document.title = "2048";
    }

    start() {
        this.drawBg();
        this.init();
    }

    protected onEnable(): void {
        this.node.on('move', this.move, this);
        // 失焦时暂停计时
        game.canvas.addEventListener('blur', this.onGameBlur);
        game.canvas.addEventListener('focus', this.onGameFocus);
    }


    protected onDisable(): void {
        this.node.off('move', this.move, this);
        game.canvas.removeEventListener('blur', this.onGameBlur);
        game.canvas.removeEventListener('focus', this.onGameFocus);
    }

    update(deltaTime: number) {
        
    }

    // 绘背景 4x4空白格子
    drawBg() {
        this.blockSize = (view.getDesignResolutionSize().width - this.gap * (size + 1)) / size;
        const blockSize = this.blockSize;
        let x = -(this.gap + blockSize) * 2 + (blockSize + this.gap) / 2;
        let y = (this.gap + blockSize) * 2 - (blockSize + this.gap) / 2;
        this._positionsNodes = [];
        // let n = 1
        for (let i = 0; i < size; i++) {
            this._positionsNodes[i] = []
            for (let j = 0; j < size; j++) {
                const block = instantiate(this.blockPrefab);
                block.setPosition(x, y);
                block.setScale(blockSize / 200, blockSize / 200, 1);
                const blockComp = block.getComponent(Block);
                this.bg.addChild(block);
                blockComp.setNumber(0);
                x += this.gap + blockSize;
                this._positionsNodes[i][j] = block;
            }
            x = -(this.gap + blockSize) * 2 + (blockSize + this.gap) / 2;
            y -= this.gap + blockSize;
        }
    }

    // 初始化游戏
    async init() {
        this.fireworks.active = false;
        this.restartPanel.active = false;
        this.resetScore();

        for (let i = 0; i < size; i++) {
            if (this._data[i] == null) {
                this._data[i] = [];
                this._blocks[i] = [];
            }
            for (let j = 0; j < size; j++) {
                this._data[i][j] = 0;
                // 如果有数据，先清除
                if (this._blocks[i]?.[j]) {
                    this._blocks[i][j].destroy();
                }
                this._blocks[i][j] = null;
            }
        }

        // 初始生成两个方块
        await Promise.all([this.randomBlock(), this.randomBlock()])
        this.hasMoved = false;
        this.toMoves = [];
        this.isGameStart = true;
        this.isGameWon = false;
        this.canControl = true;
        this.startTime = Date.now();
    }
    
    updateScore(score: number) {
        // 如果是2048 则胜利 等播放完移动动画后再显示游戏结束画面
        if (score === 2048) {
            this.isGameWon = true;
        }
        ScoreManager.getInstance().addScore(score);
        this.scoreLabel.string = ScoreManager.getInstance().getScore().toString();
        this.bestScoreLabel.string = ScoreManager.getInstance().getBestScore().toString();
    }

    resetScore() {
        ScoreManager.getInstance().resetScore();
        this.scoreLabel.string = ScoreManager.getInstance().getScore().toString();
        this.bestScoreLabel.string = ScoreManager.getInstance().getBestScore().toString();
    }

    // 找出所有空位置
    getEmptyLocations() {
        const locations = [];
        for (let i = 0; i < size; i++) {
            for (let j = 0; j < size; j++) {
                if (this._data[i][j] === 0) {
                    locations.push([i, j]);
                }
            }
        }
        return locations;
    }

    // 随机生成一个数字方块 2或4
    randomBlock() {
        const locations = this.getEmptyLocations();
        if (!locations.length) {
            return;
        }
        const index = Math.floor(Math.random() * locations.length);
        const location = locations[index];
        // 官方的算法是 2和4两个数随机 2的概率大一些(90%的概率) 4的概率小一些
        const number = numbers[Math.random() < 0.9 ? 0 : 1];
        this._data[location[0]][location[1]] = number;
        return this.createBlockWithTween(location[0], location[1], number);
    }

    // 创建一个方块 并播放动画
    createBlockWithTween(i: number, j: number, number: number) {
        const block = instantiate(this.blockPrefab);
        block.setPosition(this._positionsNodes[i][j].position);
        block.setScale(0.1, 0.1, 1);
        const blockComp = block.getComponent(Block);
        this.bg.addChild(block);
        blockComp.setNumber(number);
        this._blocks[i][j] = block;
        return new Promise((reslove, _reject) => {
            tween(block)
            .to(game.frameTime / 100, { scale: new Vec3(this.blockSize / 200, this.blockSize / 200, 1) })
            .call(() => {
                   reslove(true)
               })
            .start()
        })

    }

    // 操作移动 接受4个方向 上 下 左 右
    move(direction: Vec3) {
        if (!this.canControl) {
            return;
        }
        this.canControl = false;
        const toMoveRows = Array.from(Array(size).keys())
        // 如果是下移 则从下往上遍历
        if (direction.y === 1) {
            toMoveRows.reverse();
        }
        const toMoveCols = Array.from(Array(size).keys())
        // 如果是右移 则从右往左遍历
        if (direction.x === 1) {
            toMoveCols.reverse();
        }
        toMoveRows.forEach((i) => {
            toMoveCols.forEach((j) => {
                if (this._data[i][j]!== 0) {
                    this.toMoves.push({ oldLocation: [i,j] });
                }
            })
        })
        this.doMove(direction)
    }

    async doMove(direction: Vec3) {
        if (this.toMoves.length === 0) {
            return;
        }

        // 所有需要移动的方块全部移动计算出新位置
        for (let k = 0; k < this.toMoves.length; k++) {
            this.moveOne(k, this.toMoves[k].oldLocation[0], this.toMoves[k].oldLocation[1], direction);
        }

        // 计算完新位置后 有变化的播放动画
        const tweenPromises: Promise<any>[] = 
            this.toMoves.filter((item) => item.newLocation && !(item.newLocation[0] === item.oldLocation[0] && item.newLocation[1] === item.oldLocation[1]))
            .map((item) => this.playMove(item.oldLocation, item.newLocation))
        // 等待所有动画结束
        await Promise.all(tweenPromises)
        //  对_blocks 重新赋值
        this.toMoves.forEach((moveCell) => {
            if (moveCell.mergeDeleted) {
                // 把被合并的方块删掉
                this._blocks[moveCell.oldLocation[0]][moveCell.oldLocation[1]].destroy();
                this._blocks[moveCell.oldLocation[0]][moveCell.oldLocation[1]] = null;
            } else {
                this._blocks[moveCell.newLocation[0]][moveCell.newLocation[1]] = this._blocks[moveCell.oldLocation[0]][moveCell.oldLocation[1]];
                if (moveCell.merged) {
                    this._blocks[moveCell.newLocation[0]][moveCell.newLocation[1]].getComponent(Block).setNumber(this._data[moveCell.newLocation[0]][moveCell.newLocation[1]]);
                }
            }
        })
        // 把_data值为0的_blocks 置为null
        this.toMoves.forEach((moveCell) => {
            if (moveCell.newLocation && this._data[moveCell.oldLocation[0]][moveCell.oldLocation[1]] === 0) {
                this._blocks[moveCell.oldLocation[0]][moveCell.oldLocation[1]] = null;
            }
        })

        // 继续下一轮游戏逻辑
        this.afterMove()
    }

    // 移动方块后要做的事 判断游戏是否继续 重叠变量
    async afterMove() {
        this.toMoves = [];
        if (this.isGameWon) {
            this.isGameStart = false;
            this.canControl = false;
            this.restartPanel.active = true;
            this.fireworks.active = true;
            this.gameOverLabel.string = "You Win!!!";
            this.gameOverLabel.color = color("#00ff00");
            const gameTime = Date.now() - this.startTime;
            ScoreManager.getInstance().addGameTime(gameTime);
            const totalGameSeconds = Math.floor(ScoreManager.getInstance().getGameTime() / 1000);
            this.gameTimeLabel.string = `游戏用时：${(totalGameSeconds >= 60 ? Math.trunc(totalGameSeconds / 60) + 'm' : '')}${((totalGameSeconds % 60) > 0 ? (totalGameSeconds % 60) + 's' : '')} 总步数：${ScoreManager.getInstance().getGameStep()}次`;
            return;
        }
        // 有过移动 则生成随机方块
        if (this.hasMoved) {
            this.hasMoved = false;
            ScoreManager.getInstance().addGameStep();
            await this.randomBlock();
            // 判断游戏是否结束
            if (!this.canMove()) {
                this.isGameStart = false;
                this.canControl = false;
                this.restartPanel.active = true;
                this.gameOverLabel.string = "Game Over";
                this.gameOverLabel.color = color("#ff0000");
                const gameTime = Date.now() - this.startTime;
                ScoreManager.getInstance().addGameTime(gameTime);
                const totalGameSeconds = Math.floor(ScoreManager.getInstance().getGameTime() / 1000);
                this.gameTimeLabel.string = `游戏用时：${(totalGameSeconds >= 60 ? Math.trunc(totalGameSeconds / 60) + 'm' : '')}${((totalGameSeconds % 60) > 0 ? (totalGameSeconds % 60) + 's' : '')} 总步数：${ScoreManager.getInstance().getGameStep()}次`;
                return;
            }
        }
        this.canControl = true;
    }

    // 移动一个方块
    moveOne(index: number, i: number, j: number, direction: Vec3) {
        // 有两种停止情况 1撞墙 2撞到其他数字（如果是相同数字还要合并）
        const newI = i + direction.y;
        const newJ = j + direction.x;
        // 越界判断
        if (newI < 0 || newI >= size || newJ < 0 || newJ >= size) {
            this.toMoves[index].newLocation = [i, j]
            return;
        }

        // 移动方向的下一个位置为空
        if (this._data[newI][newJ] === 0) {
            this._data[newI][newJ] = this._data[i][j];
            this._data[i][j] = 0;
            // 递归继续移动
            this.moveOne(index, newI, newJ, direction);
            // 移动方向的下一个位置有相同数字 且没有合并过
        } else if (this._data[newI][newJ] === this._data[i][j]
            && !this.toMoves.filter(item => item.merged).some((merge) => merge.newLocation[0] === newI && merge.newLocation[1] === newJ)
        ) {
            // 两个方块重叠当然要删掉一个 把合并的位置原方块标记为将删除，当前方块标记为合并
            const mergeDeletedCell = this.toMoves.find((move) => move.newLocation[0] === newI && move.newLocation[1] === newJ)
            mergeDeletedCell.mergeDeleted = true;

            const mergeNumber = this._data[i][j] * 2;
            this._data[newI][newJ] = mergeNumber;
            this.updateScore(mergeNumber);
            this._data[i][j] = 0;

            this.toMoves[index].newLocation = [newI, newJ]
            this.toMoves[index].merged = true;
        } else {
            this.toMoves[index].newLocation = [i, j]
        }
    }

    // 移动动画
    playMove(oldLocation: number[], newLocation: number[]) {
        return new Promise((resolve, _reject) => {
            tween(this._blocks[oldLocation[0]][oldLocation[1]])
            .to(game.frameTime * 2 / 1000, { position: this._positionsNodes[newLocation[0]][newLocation[1]].position })
            .call(() => {
                    this.hasMoved = true;
                    resolve(true);
                    })
            .start()
        })
    }

    // 判断游戏是否还可以移动
    canMove() {
        for (let i = 0; i < size; i++) {
            for (let j = 0; j < size; j++) {
                const n = this._data[i][j];
                // 有空位置
                if (n === 0) {
                    return true;
                }
                // 有相邻的相同数字
                if (i > 0 && this._data[i-1][j] === n) {
                    return true;
                }
                if (i < size - 1 && this._data[i+1][j] === n) {
                    return true;
                }
                if (j > 0 && this._data[i][j-1] === n) {
                    return true;
                }
                if (j < size - 1 && this._data[i][j+1] === n) {
                    return true;
                }
            }
        }
        return false;
    }

    openUrl() {
        window.open('https://gitee.com/splendorjiang/cocos-2048'); 
    }

    onGameBlur = () => {
        if (this.isGameStart) {
            const gameTime = Date.now() - this.startTime;
            ScoreManager.getInstance().addGameTime(gameTime);
        }
    }

    onGameFocus = () => {
        if (this.isGameStart) {
            this.startTime = Date.now();
        }
    }

}
